package Simulator;

import java.util.LinkedList;
import java.util.List;

public class Simulator {
	private List<Memory> memList;
	private List<Processor> processorsList;
	private List<Instruction> instructionsList;
	private Protocols prot = new Protocols();

	/*
	 * state 1 - MODIFY 2 - SHARED 3 - INVALID instruction 0 - JOLLY - change
	 * block status 1 - PrRd 2 - PrWr 3 - BusRd 4 - BusRdX 5 - BusUpgr 6 - Flush
	 * 7 - C-C read 8 - C-M read 9 - INVALID
	 */
	final int DIRECT_MAPPED = 0;
	final int SET_ASSOCIATIVE = 1;
	final int MSI = 0;
	final int MESI = 1;
	final int MODIFY = 1;
	final int SHARED = 2;
	final int INVALID = 3;
	final int EXCLUSIVE = 4;
	final int JOLLY = 0;
	final int PR_RD = 1;
	final int PR_WR = 2;
	final int BUS_RD = 3;
	final int BUS_RD_X = 4;
	final int BUS_UPGR = 5;
	final int FLUSH = 6;
	final int FLUSH_FREE = 7;
	final int C_C = 7;
	final int M_C = 8; // memory to cache: load a block into a cache
	int PROTOCOL_TYPE = MSI;
	int CACHE_TYPE = DIRECT_MAPPED;

	private Settings settings;

	public Simulator(Settings settings) {
		this.settings = settings;
		processorsList = new LinkedList<Processor>();
		switch (settings.getCacheType()) {
		case DIRECT_MAPPED:
			CACHE_TYPE = DIRECT_MAPPED;
		case SET_ASSOCIATIVE:
			CACHE_TYPE = SET_ASSOCIATIVE;
		}
		switch (settings.getProtocolType()) {
		case MSI:
			PROTOCOL_TYPE = MSI;
		case MESI:
			PROTOCOL_TYPE = MESI;
		}
		for (int i = 0; i < settings.getProcNum(); i++) {
			processorsList.add(new Processor(settings, i));
		}
	}

	public List<Instruction> start(String instruction) {
		instructionsList = new LinkedList<Instruction>();
		String vett[] = instruction.split("[,]");
		// trasformato in w->PR_WR r->PR_RD
		// w = 0 ; r = 1
		Msg m = new Msg(Integer.parseInt(vett[0]), (vett[1]
				.equalsIgnoreCase("w") ? PR_WR : PR_RD), vett[2],
				(vett.length == 4 ? vett[3] : ""));
		Processor toProcessor = processorsList.get(m.to);
		Block startBlock = getBlock(m.address, m.to);
		if (startBlock == null) {
			startBlock = (getBlockFromId(m.address, m.to));
			if (getBlockFromId(m.address, m.to).state != INVALID) {
				String oldAddress = padAddressRight(getBlockFromId(m.address,
						m.to).tag);
				// do operation to invalid the block
				if (settings.getProtocolType() == MSI) {
					// MSI
					if (getBlockFromId(m.address, m.to).state == MODIFY)
						instructionsList.addAll((Flush(m.to, oldAddress,
								BUS_RD_X, false)));
				} else if (settings.getProtocolType() == MESI) {
					// MESI
					if (getBlockFromId(m.address, m.to).state == MODIFY)
						instructionsList.addAll((Flush(m.to, oldAddress,
								BUS_RD_X, true)));
					if (getBlockFromId(m.address, m.to).state == SHARED)
						if (!getBlockFromId(m.address, m.to).data
								.equals(getMemory(m.address).value)) {
							instructionsList.addAll((Flush(m.to, oldAddress,
									BUS_RD_X, false)));
						}
				}
			}
			startBlock.state = INVALID;

		}
		switch (prot.mesiMap.get(Integer.parseInt(startBlock.state + ""
				+ m.operation))) {
		case BUS_RD: {
			instructionsList.addAll(BusRd(m.to, m.address));
			break;
		}
		case BUS_RD_X: {
			instructionsList.addAll(BusRdX((m.to), m.address));
			break;
		}
		case BUS_UPGR: {
			instructionsList.addAll(BusUpgr(m.to, m.address));
			break;
		}
		default: {
			if (m.operation == PR_RD) {
				Block myBlock = getBlock(m.address, (m.to));
				instructionsList.add(new Instruction(toProcessor.id, PR_RD,
						myBlock));
			}
		}

		}
		if (m.operation == PR_WR) {
			Block myBlock = getBlock(m.address, (m.to));
			String binAddress = hexToBinAddress(m.address);
			String offset = binAddress.substring(settings.getBitAddress()
					- settings.getBitOffset());
			int intOffs = Integer.parseInt(offset, 2);
			String shiftValue = m.value;
			if (shiftValue.length() > (settings.getBlockSize() * 2)) {
				shiftValue = shiftValue.substring(0,
						settings.getBlockSize() * 2);
			} else {
				for (int i = 0; i < intOffs; i++) {
					shiftValue = "0" + shiftValue;
				}
				for (int i = shiftValue.length(); i < settings.getBlockSize() * 2; i++) {
					shiftValue += "0";
				}
			}

			myBlock.data = shiftValue;
			myBlock.state = MODIFY;
			myBlock.used = true;
			setBlock((m.to), m.address, myBlock);
			instructionsList.add(new Instruction(toProcessor.id, PR_WR,
					getBlock(m.address, m.to)));
		}
		return instructionsList;
	}

	public List<Instruction> BusUpgr(int idPr, String address) {
		List<Instruction> myList = new LinkedList<Instruction>();
		myList.add(new Instruction(0, BUS_UPGR, null));
		Block myBlock;
		for (int i = 0; i < settings.getProcNum(); i++) {
			myBlock = getBlock(address, i);
			if (myBlock == null)
				continue;
			if ((i != idPr) && (myBlock.state != INVALID)) {
				myBlock.state = INVALID;
				myList.add(new Instruction(i, JOLLY, myBlock));
			}
		}
		return myList;
	}

	public List<Instruction> BusRdX(int idPr, String address) {
		int blockId = getBlockId(address, idPr);
		List<Instruction> myList = new LinkedList<Instruction>();
		myList.add(new Instruction(idPr, BUS_RD_X,
				getBlockFromId(address, idPr)));
		boolean done = false;
		for (int i = 0; i < settings.getProcNum(); i++) {
			Block myBlock;
			if (getBlock(address, i) != null) {
				myBlock = getBlock(address, i);
				if (idPr != i) {
					if ((myBlock.state == MODIFY)) {
						// M-status
						myList.addAll((Flush(idPr, address, BUS_RD_X, true)));
						done = true;
					}
					else if ((myBlock.state == EXCLUSIVE)) {
						myBlock.state = MODIFY;
						myBlock.used = true;
						setBlock(idPr, address, new Block(myBlock));
						myList.add(new Instruction(idPr, C_C, getBlock(address,
								idPr)));
						processorsList.get(i).changeUsed(
								getBlock(address, i).id);
						Block stupidoBlock = new Block(getBlock(address, i));
						stupidoBlock.state = INVALID;
						myList.add(new Instruction(i, JOLLY, stupidoBlock));
						getBlockFromId(address, i).state = INVALID;
						done = true;
					} else if ((myBlock.state == SHARED)) {
						// S-status
						if (done == false) {
							// copy the block of the i-processor in the idPr
							// cache
							myBlock.state = MODIFY;
							myBlock.used = true;
							setBlock(idPr, address, new Block(myBlock));
							myList.add(new Instruction(idPr, C_C, getBlock(
									address, idPr)));
							done = true;
						}
						// invalid the block of the i-cache
						processorsList.get(i).changeUsed(
								getBlock(address, i).id);
						Block stupidoBlock = new Block(getBlock(address, i));
						stupidoBlock.state = INVALID;
						myList.add(new Instruction(i, JOLLY, stupidoBlock));
						getBlockFromId(address, i).state = INVALID;
					} else {
						// I-status
					}
				}
			}
		}
		if (done == false) { // no block in processors's cache
			Memory myMem = getMemory(address);
			Block myBlock = new Block(blockId, MODIFY,
					getTagFromAddress(address), myMem.value, true);
			setBlock(idPr, address, myBlock);
			myList.add(new Instruction(idPr, M_C, myMem));
		}
		return myList;
	}

	public List<Instruction> BusRd(int idPr, String address) {
		int blockId = getBlockId(address, idPr);
		List<Instruction> myList = new LinkedList<Instruction>();
		myList
				.add(new Instruction(idPr, BUS_RD,
						getBlockFromId(address, idPr)));
		for (int i = 0; i < settings.getProcNum(); i++) {
			Block myBlock;
			if (getBlock(address, i) != null)
				myBlock = getBlock(address, i);
			else
				myBlock = getBlockFromId(address, i);
			if ((i != idPr) && (myBlock.state == MODIFY)) {
				myList.addAll((Flush(idPr, address, BUS_RD, true)));
				return myList;
			} else if ((i != idPr)
					&& (processorsList.get(i).getBlockState(address) == SHARED)) {
				// S-status
				// copy the block of the i-processor in the idPr cache
				myBlock.state = SHARED;
				myBlock.used = true;
				setBlock(idPr, address, new Block(myBlock));
				myList.add(new Instruction(idPr, C_C, getBlock(address, idPr)));
				return myList;
			} else if ((i != idPr)
					&& (processorsList.get(i).getBlockState(address) == EXCLUSIVE)) {
				myList.addAll((Flush(idPr, address, BUS_RD, true)));
				return myList;
			}
		}
		// if no block in cache
		Memory myMem = getMemory(address);
		Block myBlock = new Block(blockId, SHARED, getTagFromAddress(address),
				myMem.value, true);
		if (settings.getProtocolType() == MESI) {
			myBlock.state = EXCLUSIVE;
			myBlock.used = true;
		}
		setBlock(idPr, address, myBlock);
		myList.add(new Instruction(idPr, M_C, myMem));
		return myList;
	}

	public List<Instruction> Flush(int idPr, String oldAddress, int operation,
			boolean doCC) {
		List<Instruction> myList = new LinkedList<Instruction>();
		Memory myMem = null;
		for (int i = 0; i < settings.getProcNum(); i++) {
			Block myBlock = getBlock(oldAddress, i);
			if (((i != idPr) || (!doCC))
					&& (myBlock != null)
					&& ((myBlock.state == MODIFY) || (myBlock.state == EXCLUSIVE))) {
				myMem = setBlockIntoMemory(oldAddress,
						getBlock(oldAddress, i).data);
				myList.add(new Instruction(i, FLUSH, myMem));
				// copy the block in the memory
				// add this instruction in myList
				if (operation == BUS_RD) { // BusRd : block status -> S
					getBlock(oldAddress, i).state = SHARED; // change block
					// status
					processorsList.get(i)
							.changeUsed(getBlock(oldAddress, i).id);
					myList.add(new Instruction(i, JOLLY,
							getBlock(oldAddress, i)));
					if (doCC) {
						setBlock(idPr, oldAddress, getBlock(oldAddress, i)); // copy
						// the
						// block
						myList.add(new Instruction(idPr, C_C, getBlock(
								oldAddress, i)));
					}
				}
				if (operation == BUS_RD_X) { // BusRdX : block status -> I
					if (doCC) {
						setBlock(idPr, oldAddress, new Block(getBlock(
								oldAddress, i))); // copy
						// the
						// block
						getBlock(oldAddress, idPr).state = MODIFY; // change
						// block
						// state
						myList.add(new Instruction(idPr, C_C, new Block(
								getBlock(oldAddress, i))));
					}
					processorsList.get(i)
							.changeUsed(getBlock(oldAddress, i).id);
					myList.add(new Instruction(i, JOLLY,
							getBlock(oldAddress, i)));
					getBlock(oldAddress, i).state = INVALID; // change block
					// status
				}
			}
		}

		return myList;
	}

	/*
	 * cacheType: id type 0 direct mapped 1 set associative 2-way
	 */
	public List<Memory> setMemory() {
		memList = new LinkedList<Memory>();
		// # memory blocks
		int nBlock = (settings.getMemSize() / settings.getBlockSize());
		String padding = "";
		for (int k = 0; k < settings.getBlockSize() * 2; k++)
			padding += "0";

		for (int i = 0; i < nBlock; i++) {
			int address = i * settings.getBlockSize() * 2; // tag
			Memory myMem = new Memory(Integer.toHexString(address), padding, i);
			memList.add(myMem);
		}
		return memList;
	}

	public Memory getMemory(String address) {
		String sourceTag = getTagFromAddress(address);
		for (Memory m : memList) {
			if (sourceTag.equals(getTagFromAddress(m.label)))
				return m;
		}
		return null;

	}

	public String getBlockValueFromMemory(String address) {
		String sourceTag = getTagFromAddress(address);
		for (Memory m : memList) {
			if (sourceTag.equals(getTagFromAddress(m.label))) {
				return m.value;
			}
		}
		return null;
	}

	public String hexToBinAddress(String address) {
		String binAddress = Integer.toBinaryString(Integer
				.parseInt(address, 16));
		while (binAddress.length() < settings.getBitAddress()) {
			binAddress = "0" + binAddress;
		}
		return binAddress;
	}

	public String padAddressRight(String address) {
		int add = Integer.parseInt(address, 16);
		add = add * (int) Math.pow(2, (double) settings.getBitOffset());
		address = Integer.toHexString(add);
		return address;
	}

	public String getTagFromAddress(String address) {
		address = hexToBinAddress(address);
		String tag = address.substring(0, settings.getBitTag());
		tag = Integer.toHexString(Integer.parseInt(tag, 2));
		return tag;
	}

	public Memory setBlockIntoMemory(String address, String value) {
		String sourceTag = getTagFromAddress(address);
		for (Memory m : memList) {
			if (sourceTag.equals(getTagFromAddress(m.label))) {
				m.value = value;
				return m;
			}
		}
		return null;
	}

	public Block getBlock(String address, int processorId) {
		if (settings.getCacheType() == DIRECT_MAPPED) {
			return processorsList.get(processorId)
					.getDirectMappedBlock(address);
		} else {
			return processorsList.get(processorId).getSetAssociativeBlock(
					address);
		}
	}

	public Block getBlockFromId(String address, int processorId) {
		if (settings.getCacheType() == DIRECT_MAPPED) {
			return processorsList.get(processorId).getDirectMappedBlockFromId(
					Integer.toBinaryString(Integer.parseInt(address, 16)));
		} else {
			return processorsList.get(processorId)
					.getSetAssociativeBlockFromId(
							Integer.toBinaryString(Integer
									.parseInt(address, 16)));
		}
	}

	public int getBlockId(String address, int processorId) {
		if (settings.getCacheType() == DIRECT_MAPPED) {
			return processorsList.get(processorId).getDirectMappedBlockId(
					address);
		} else {
			return processorsList.get(processorId).getSetAssociativeBlockId(
					address);
		}
	}

	public void setBlock(int processorId, String address, Block myBlock) {
		if (settings.getCacheType() == DIRECT_MAPPED) {
			processorsList.get(processorId).setDirectMappedBlock(address,
					myBlock);
		} else {
			processorsList.get(processorId).setSetAssociativeBlock(address,
					myBlock);
		}
	}

	public static void main(String args[]) {
		new Thread(new GraphicT()).start();
	}
}